#include "cas.hpp"
#include "as_def.hpp"

#include <iostream>
#include <fstream>
#include <sstream>
#include <cctype>
#include <algorithm>

void Cas::init(std::string input_filename, std::string output_filename) {
    std::ifstream input_file(input_filename);
    std::string content((std::istreambuf_iterator<char>(input_file)), std::istreambuf_iterator<char>());
    src = content;
    index = 0;
    output_file.open(output_filename, std::ios::binary);

    long code_long = 0;
    output_file.write(reinterpret_cast<const char*>(&code_long), sizeof(code_long));
}

bool Cas::is_whitespace() {
    if(src[index] == ' ' || src[index] == '\t' || src[index] == '\n' || src[index] == '\r' || index >= src.length()) {
        return true;
    }
}

void Cas::write_type(int type) {
    output_file.write(reinterpret_cast<const char*>(&type), sizeof(type));
}

void Cas::write_str(std::string str) {
    int str_long = str.length();
    output_file.write(reinterpret_cast<const char*>(&str_long), sizeof(int));
    output_file.write(str.c_str(), str.length());
}

void Cas::compile_num() {
    index ++;   // @

    std::string num = "";
    while(! is_whitespace()) {
        num += src[index ++];
    }

    double target = std::stof(num);
    write_type(AS_NUM);
    output_file.write(reinterpret_cast<const char*>(&target), sizeof(target));
}

void Cas::compile_addr() {
    index ++;   // %

    std::string addr = "";
    while(! is_whitespace()) {
        addr += src[index ++];
    }

    write_type(AS_ADDR);
    write_str(addr);
}

void Cas::compile_var() {
    index ++;   // $

    std::string var = "";
    while(! is_whitespace()) {
        var += src[index ++];
    }

    write_type(AS_VAR);
    write_str(var);
}

void Cas::compile_label() {
    index ++;   // !

    std::string label = "";
    while(! is_whitespace()) {
        label += src[index ++];
    }

    label_msg[label] = output_file.tellp();
}

void Cas::compile_direct() {
    std::string direct = "";
    
    while(index < src.length() && isalpha(src[index])) {
        direct += src[index ++];
    }

    write_type(AS_DIRECT);
    #define write_direct write_type
    std::transform(direct.begin(), direct.end(), direct.begin(), ::toupper);
    if(direct == "LOAD") {
        write_direct(DIRECT_LOAD);
    } else if(direct == "STORE") {
        write_direct(DIRECT_STORE);
    } else if(direct == "CALC") {
        write_direct(DIRECT_CALC);
    } else if(direct == "JMP") {
        write_direct(DIRECT_JMP);
    } else if(direct == "JC") {
        write_direct(DIRECT_JC);
    } else if(direct == "CALL") {
        write_direct(DIRECT_CALL);
    } else if(direct == "RET") {
        write_direct(DIRECT_RET);
    } else if(direct == "TABLE") {
        write_direct(DIRECT_TABLE);
    } else if(direct == "FN") {
        write_direct(DIRECT_FN);
    } else {
        std::cerr << "ERROR: undefined direct: " << direct << std::endl;
        exit(-1);
    }
}

void Cas::compile_str() {
    index ++;   // "

    std::string str = "";
    while(src[index] != '"' && index < src.length()) {
        if(src[index] == '\\' && (src[index + 1] == '\\' || src[index + 1] == '"')) {
            index ++;
        }

        str += src[index ++];
    }

    index ++;   // "
    write_type(AS_STR);
    write_str(str);
}

void Cas::compile_option() {
    index ++;   // [

    std::string option = "";
    while(src[index] != ']' && index < src.length()) {
        option += src[index ++];
    }

    index ++;   // ]
    write_type(AS_OPTION);
    write_str(option);
}

void Cas::compile_comment() {
    while(src[index ++] != '}');
    index ++;
}

void Cas::compile() {
    while(index < src.length()) {
        switch(src[index]) {
            case '@': {
                compile_num();
                break;
            }

            case '"': {
                compile_str();
                break;
            }

            case '$': {
                compile_var();
                break;
            }

            case '%': {
                compile_addr();
                break;
            }

            case '[': {
                compile_option();
                break;
            }

            case '!': {
                compile_label();
                break;
            }

            case '{': {
                compile_comment();
                break;
            }

            case ' ':
            case '\t':
            case '\n': 
            case '\r': {
                index ++;
                continue;
            }

            default: {
                compile_direct();
                break;
            }
        }
    }
}

void Cas::write_filemsg() {
    long code_long = output_file.tellp();
    output_file.seekp(0, std::ios::beg);
    output_file.write(reinterpret_cast<const char*>(&code_long), sizeof(code_long));
    output_file.seekp(0, std::ios::end);

    int label_num = label_msg.size();
    output_file.write(reinterpret_cast<const char*>(&label_num), sizeof(label_num));
    for(auto label : label_msg) {
        write_str(label.first);
        output_file.write(reinterpret_cast<const char*>(&(label.second)), sizeof(label.second));
    }
}

#ifdef DEBUG
int main() {
    Cas cas;
    cas.init("test.s", "test.bin");
    cas.compile();
    cas.write_filemsg();
}
#endif